<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>JAVA—线程同步器AQS | 海晨忆的博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
  
  
  <meta name="description" content="前言&amp;emsp;&amp;emsp;每一项技术的提出都是为了解决某一个问题，带着问题来理解技术，使得印象你对这个技术的理解印象更加深刻。">
<meta property="og:type" content="article">
<meta property="og:title" content="JAVA—线程同步器AQS">
<meta property="og:url" content="https://haichenyi.com/2021/07/19/JAVA—线程同步器AQS/index.html">
<meta property="og:site_name" content="海晨忆的博客">
<meta property="og:description" content="前言&amp;emsp;&amp;emsp;每一项技术的提出都是为了解决某一个问题，带着问题来理解技术，使得印象你对这个技术的理解印象更加深刻。">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2021-07-19/worker图片.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2021-07-19/秒杀图1.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2021-07-19/秒杀图2.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2021-07-19/加锁流程图.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2021-07-19/最终运行结果图.png">
<meta property="og:updated_time" content="2021-07-19T14:48:47.953Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="JAVA—线程同步器AQS">
<meta name="twitter:description" content="前言&amp;emsp;&amp;emsp;每一项技术的提出都是为了解决某一个问题，带着问题来理解技术，使得印象你对这个技术的理解印象更加深刻。">
<meta name="twitter:image" content="https://haichenyi.com/uploads/article/2021-07-19/worker图片.png">
  
    <link rel="alternate" href="/atom.xml" title="海晨忆的博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/uploads/artistic_image/head.jpg">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  <link rel="stylesheet" href="/css/highlight.css">
  <script>
  let antiquityStorage = window.sessionStorage.getItem('antiquitySessionStorage');
  if (antiquityStorage == '' || antiquityStorage == null) {
    var antiquityLoader = '<div id="loaderbox"><div class="loader"><div class="load-roll"><div class="load-top"></div><div class="load-right"></div><div class="load-bottom"></div></div></div></div>';
    document.write(antiquityLoader);
    document.body.style.overflow = 'hidden'
  }
  </script>
</head>

<body>
  <div id="fullpage" class="mobile-nav-right">
    
      <div id="wrapper" style="background-image: url(/uploads/artistic_image/bg.jpg)" title="背景图片来自网络">
    
    
      <header id="header">
  <div id="nav-toggle" class="nav-toggle"></div>
  <div class="head-box global-width">
    <nav class="nav-box nav-right">
      
        <a class="nav-item" href="/" title
        
        >首页</a>
      
        <a class="nav-item" href="/archives" title
        
        >归档</a>
      
        <a class="nav-item" href="/about" title
        
        >简历</a>
      
    </nav>
  </div>
</header>
      <div id="middlecontent" title class="global-width sidebar-left">
        <section id="main"><article id="post-JAVA—线程同步器AQS" class="article global-container article-type-post" itemscope itemprop="blogPost">
  
    <header class="article-header">
      
  
    <h1 class="article-title" itemprop="name">
      JAVA—线程同步器AQS
    </h1>
  

    </header>
  
  <div class="article-meta">
    <a href="/2021/07/19/JAVA—线程同步器AQS/" class="article-date">
  <time datetime="2021-07-19T14:41:15.000Z" itemprop="datePublished">2021-07-19</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/JAVA-并发/">JAVA -并发</a>
  </div>

    
  </div>
  
    <span id="busuanzi_container_page_pv">
      本文总阅读量<span id="busuanzi_value_page_pv"></span>次
    </span>
  

  <div class="article-inner">
    
    <div class="article-content article-content-cloud doorframe mac" itemprop="articleBody">
      
        <h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>&emsp;&emsp;每一项技术的提出都是为了解决某一个问题，带着问题来理解技术，使得印象你对这个技术的理解印象更加深刻。</p>
 <a id="more"></a>
<h3 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h3><p>&emsp;&emsp;举个栗子：现在有一个需求，网络请求分两批（A,B两个批次），A批次并行请求，B批次串行请求按顺序一个一个请求，有一个总超时时间，B批次每一次请求都有一个超时时间，A批次并发请求先请求，在规定时间内没有返回，再开始请求B批次，谁先返回用谁的。</p>
<p>&emsp;&emsp;这个是我们项目里面简化过后的一个逻辑，实际逻辑，比这个还要复杂。怎么实现这个功能呢？</p>
<p>&emsp;&emsp;带着这个问题来进入我们的正题，什么是并发？</p>
<ol>
<li>并发是一种现象：同时运行多个程序或者多个任务需要被处理的现象。</li>
<li>这些任务可能是并行的，也可能是串行的，和CPU的核心数无关，是操作系统进程调度和CPU上下文切换达到的结果。</li>
<li>解决并发的思路就是把分解，把一个大任务分解成多个小任务来执行</li>
</ol>
<p>&emsp;&emsp;像我们上面所说的需求就是分解成一个一个的网络请求，一部分并行请求，一部分串行请求。并行请求简单来说，对应我们应用里面就是多线程，多线程同时执行；串行请求对应我们应用里面就是单线程，一个线程执行完了，另一个线程才开始。这里不考虑多进程的问题。</p>
<h3 id="并发为什么会造成线程不安全的问题"><a href="#并发为什么会造成线程不安全的问题" class="headerlink" title="并发为什么会造成线程不安全的问题"></a>并发为什么会造成线程不安全的问题</h3><p>&emsp;&emsp;我们先来聊聊cpu是怎么执行指令：</p>
<ol>
<li>首先，cpu执行指令的过程中，不可避免会执行读写操作，而这个操作都是从主存（也就是物理内存）中去读写</li>
<li>但是，cpu执行指令速度很快，程序运行过程中的临时变量都是放在主存当中的，如果全部都是从主存中去读写，读写很耗时，这样就浪费了cpu的性能</li>
<li>最后，为了解决这个问题，就出现了高速缓存的概念。我们先把变量读取到告诉缓存中，然后，再高速缓存中操作完之后，再刷新到主存当中。</li>
</ol>
<p>&emsp;&emsp;java的内存模型规定，所有的变量都在主存当中，类似于物理内存，每个线程都有自己的工作空间，也就是对应上面的高速缓存，每一个线程都有一个自己的高速缓存。</p>
<p>&emsp;&emsp;线程对变量的操作必须在自己的工作空间内，不能直接操作主存，而且，一个线程也不能访问另一个线程的工作空间。</p>
<p>&emsp;&emsp;那么，我们如果多个线程同时对一个变量做加1操作，如下面的add1方法。我们thread1和thread2把a的值同时复制到自己的工作空间中时，都是0，然后同时进行加1操作，同时刷新到主存当中，那最后，我们获取到的最终的值就是1，而不是我们想要的2.这就是并发造成的线程不安全的问题</p>
<h3 id="简单的同步器"><a href="#简单的同步器" class="headerlink" title="简单的同步器"></a>简单的同步器</h3><p>&emsp;&emsp;我们项目里面遇到的并发问题，基本上就是多线程访问同一变量的问题，比方说，简单的举个栗子，两个线程对同一个int值做加1操作，然后打印出来。</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">public int a = 0;</span><br><span class="line">@org.junit.Test</span><br><span class="line">public void add1() &#123;</span><br><span class="line">    Thread thread1 = new Thread(new Runnable() &#123;</span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            a++;</span><br><span class="line">            System.out.println(&quot;thread1:a=&quot; + a);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    Thread thread2 = new Thread(new Runnable() &#123;</span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            a++;</span><br><span class="line">            System.out.println(&quot;thread2:a=&quot; + a);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    thread1.start();</span><br><span class="line">    thread2.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;因为这里直接用的基本类型，极端情况下，线程1，线程2同时执行，里面的a++操作也是同时执行，那这里两个打印都是1，虽然，这里我没有复现出来。但是，这种情况肯定是存在的。那么，怎么避免这种情况呢？</p>
<p>&emsp;&emsp;我们可以写一个简单的线程同步器，就是加锁操作，如下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">private Object lock = new Object();</span><br><span class="line">public int a = 0;</span><br><span class="line">@org.junit.Test</span><br><span class="line">public void add1()&#123;</span><br><span class="line">    Thread thread1 = new Thread(new Runnable() &#123;</span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            //先获取到lock 对象的锁</span><br><span class="line">            synchronized (lock)&#123;</span><br><span class="line">                try &#123;</span><br><span class="line">                 //在lock对象上执行wait()方法,让其进入休眠,等待有人唤醒自己</span><br><span class="line">                    lock.wait();</span><br><span class="line">                &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                a++;</span><br><span class="line">                System.out.println(&quot;thread1:a=&quot; + a);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    Thread thread2 = new Thread(new Runnable() &#123;</span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">        //获取lock的锁</span><br><span class="line">            synchronized (lock)&#123;</span><br><span class="line">                a++;</span><br><span class="line">                System.out.println(&quot;thread2:a=&quot; + a);</span><br><span class="line">                //唤醒正在lock对象上等待的线程</span><br><span class="line">                lock.notify();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    thread1.start();</span><br><span class="line">    thread2.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里，我们用到的object类的wait和notify方法。等待和唤醒。当线程1执行到lock.wait();方法时，线程1会进入等待状态。当线程2执行lock.notify();时，会唤醒线程1，执行线程1的后续a++，打印操作。</p>
<p>&emsp;&emsp;那么，这里会有一个问题，如果线程2先执行，线程1后执行，那么线程1将永远的等待下去，这也是这样写的一个弊端。这还只是2个线程，实际项目中往往比这个复杂多了。为了解决这弊端，就引出了我们的<strong><em>线程同步器AQS(AbstractQueuedSynchronizer)</em></strong></p>
<p>&emsp;&emsp;并且，这个同步器，java.util包下面都已经给好了实现类，比方说：Semaphore，ReentrantLock，CountDownLatch等等都是，并且，我们用到的java线程池ThreadPoolExecutor中的Worker的实现也是。如下图：</p>
<p><img src="/uploads/article/2021-07-19/worker图片.png" alt="worker图片.png"></p>
<h3 id="线程同步器AQS-AbstractQueuedSynchronizer"><a href="#线程同步器AQS-AbstractQueuedSynchronizer" class="headerlink" title="线程同步器AQS(AbstractQueuedSynchronizer)"></a>线程同步器AQS(AbstractQueuedSynchronizer)</h3><p>&emsp;&emsp;线程同步器就是为了解决并发引起的线程不安全的问题。线程安全的三大特性：原子性，可见性，有序性。</p>
<h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>&emsp;&emsp;简单点来说就是，它维护一个状态state，还有一个CLH队列。</p>
<p>&emsp;&emsp;CLH时一个双端队列，队列中每一个节点都放着正在等待获取资源的线程。当线程现在通过CAS原子算法比较预期值的方式去获取资源，也就是判断这里的state状态，是不是有等待获取资源的线程可以使用，如果时有，那就直接使用，如果没有，那就会将这个线程封装成一个节点Node，插入到CLH队列的尾部等待被唤醒。其他线程执行完之后，调用release释放一部分资源，那么，正在等待的队列就会被唤醒，去执行自己的任务。大致是这个意思，当然，AQS还有中断等其他的操作</p>
<h4 id="简单的AQS同步器"><a href="#简单的AQS同步器" class="headerlink" title="简单的AQS同步器"></a>简单的AQS同步器</h4><p>&emsp;&emsp;还是类似于上面那个例子，不过，都是访问同一个变量，业务背景换一下，换成一个库存秒杀，通过访问服务器，一共10件物品，我现在有20个线程去同时请求，哪些能抢到，哪些不能抢到？</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">public int count = 10;</span><br><span class="line"></span><br><span class="line">    @org.junit.Test</span><br><span class="line">    public void test3() &#123;</span><br><span class="line">        for (int i = 1; i &lt;= 20; i++) &#123;</span><br><span class="line">            final int finalI = i;</span><br><span class="line">            new Thread(new Runnable() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                public void run() &#123;</span><br><span class="line">                    buy(finalI);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    public void buy(int i) &#123;</span><br><span class="line">        if (count != 0) &#123;</span><br><span class="line">            count--;</span><br><span class="line">            System.out.println(&quot;第&quot; + i + &quot;个用户抢到了，&quot; + &quot;库存还剩:&quot; + count + &quot;件&quot;);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            System.out.println(&quot;第&quot; + i + &quot;个用户抢到了，&quot; + &quot;已经被抢光了&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;就像上面这样模拟一个简单的秒杀场景，库存10件，20个用户抢，实际情况肯定不止。我们看一下这样写的打印，如下图：</p>
<p><img src="/uploads/article/2021-07-19/秒杀图1.png" alt="秒杀图1.png"></p>
<p>&emsp;&emsp;我们看到这个最终虽然有10个用户的确抢到了，但是，我们看一下打印，打印是从8个开始的，不是从9开始的。我们理想的应该是如下这个图：</p>
<p><img src="/uploads/article/2021-07-19/秒杀图2.png" alt="秒杀图2.png"></p>
<p>&emsp;&emsp;这个图是我把buy方法加上了<strong>synchronized</strong>关键字，我给它锁住了。当执行buy方法的时候，另一个线程如果也进来执行buy，它会等待，等待前一个buy方法执行完，它才开始执行。</p>
<p>&emsp;&emsp;那么，直接用这个关键字就好了呀，还要啥自行车？天真，存在即合理。synchronized关键字锁比较重，不适合这种秒杀场景。</p>
<p>&emsp;&emsp;言归正传，上面两个从8开始，结合我们前面说的并发的问题，是不是就可以联想到，如果，多个线程同时，同一时刻访问，然后，数据库的库存同时减1，是不是就会出现一件商品，卖给多个人的情况？</p>
<p>&emsp;&emsp;有同学就会想，那么这么巧，同时访问，同一时刻，那你想想天猫双十一，那些节假日的秒杀场景，会出现什么问题？</p>
<p>&emsp;&emsp;这个时候线程同步器就出现了，我们不能对用户做限制，我们不能说张三你必须在某一个时间内访问，李四在某一个时间内访问，所以，我们能控制的只有服务器，也就是这里的buy方法。</p>
<p>&emsp;&emsp;也就是说，我们这里用的是悲观锁的方式，进入buy方法就立刻加锁，运行完buy方法就解锁。后面应用里面再聊这个悲观锁，乐观锁之类的。我们就把buy方法改成如下这样了：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">    MyOwnLock ownLock = new MyOwnLock();</span><br><span class="line"></span><br><span class="line">    public void buy(int i) &#123;</span><br><span class="line">        //加锁</span><br><span class="line">        ownLock.lock();</span><br><span class="line">        if (count != 0) &#123;</span><br><span class="line">            count--;</span><br><span class="line">            System.out.println(&quot;第&quot; + i + &quot;个用户抢到了，&quot; + &quot;库存还剩:&quot; + count + &quot;件&quot;);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            System.out.println(&quot;第&quot; + i + &quot;个用户抢到了，&quot; + &quot;已经被抢光了&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">        //解锁</span><br><span class="line">        ownLock.unLock();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">/**</span><br><span class="line"> * @ClassName: MyOwnLock</span><br><span class="line"> * @Description:</span><br><span class="line"> * @Author: 海晨忆</span><br><span class="line"> * @Date: 2021/7/15 11:07</span><br><span class="line"> */</span><br><span class="line">public class MyOwnLock &#123;</span><br><span class="line">    public void lock() &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void unLock() &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;如上代码，现在，我们要做的就是完善MyOwnLock类的加锁和解锁方法。</p>
<p>&emsp;&emsp;我们想想这个流程，多个用户同时下单，实际上对于代码来讲，就是多个线程同时请求服务器，调用这里的buy方法，去减少库存，我们需要做的就是保证这里减少库存不能出问题。</p>
<p>&emsp;&emsp;怎么保证这个减少库存不能出问题呢？就是我们前面说的并发的问题，也就是这里的线程安全的问题。线程安全的三大特性：原子性，可见性，有序性。我们保证这三个特性就好了。</p>
<p>&emsp;&emsp;第一个线程进来，我们就标记一下，已经有线程进来在执行了，我们就改变这个标记，后面的线程感知到这个改变之后，就必须等待。那这个标记，怎么让其他线程感知到呢？</p>
<p>&emsp;&emsp;<strong>volatile</strong>关键字的两大特性：可见性，有序性</p>
<p>&emsp;&emsp;可见性，就是可以让其他线程感知到。那么，就解决了这个问题，我们用计数器的方式来做这个标记。我们是悲观锁的方式，始终只能有一个线程访问，必须等这个线程访问完了，其他线程才能访问。</p>
<p>&emsp;&emsp;对应成代码就是，这个计数器变量初始化是0，加锁成功之后，就加1，后面的线程进来的时候，判断这个计数器是不是0，如果不是0，就表示有线程正在访问，不能进行加锁操作；如果是0，就表示没有，可以进行加锁操作。那么我们就开始写代码：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">package com.example.myapplication;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * @ClassName: MyOwnLock</span><br><span class="line"> * @Description:</span><br><span class="line"> * @Author: 海晨忆</span><br><span class="line"> * @Date: 2021/7/15 11:07</span><br><span class="line"> */</span><br><span class="line">public class MyOwnLock &#123;</span><br><span class="line">    private volatile int state;</span><br><span class="line">    private Thread currentHolder;</span><br><span class="line"></span><br><span class="line">    public void lock() &#123;</span><br><span class="line">        Thread currentThread = Thread.currentThread();</span><br><span class="line">        int state = getState();</span><br><span class="line">        if (state == 0) &#123;</span><br><span class="line">            setCurrentHolder(currentThread);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void unLock() &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public int getState() &#123;</span><br><span class="line">        return state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setState(int state) &#123;</span><br><span class="line">        this.state = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Thread getCurrentHolder() &#123;</span><br><span class="line">        return currentHolder;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setCurrentHolder(Thread currentHolder) &#123;</span><br><span class="line">        this.currentHolder = currentHolder;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;直接用if(state==0)来判断就可以了吗？volatile关键字只能保证可见性，有序性，并不能保证原子性，所以，volatile并不是真正的线程安全，只是大多数情况下还是比较有用的，而，我们这里要保证线程安全，就需要保证原子性，原子性怎么保证呢？</p>
<p>&emsp;&emsp;这里就出现了关键的<strong>CAS算法</strong>了，<strong>Compare And Swap</strong>比较互换。这个算法java里面是怎么实现的呢？我们java里面有一个Unsafe类，他的里面全是native方法，提供的都是硬件级别的原子操作。我们用到的就是这个类里面的几个方法，CAS的操作也是这些方法实现的<strong>compareAndSwapXXX</strong>。具体的可以在网上搜一下这个类，然后仔细的看一下，这里，我简单的介绍一个：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">/***</span><br><span class="line"> * Compares the value of the object field at the specified offset</span><br><span class="line"> * in the supplied object with the given expected value, and updates</span><br><span class="line"> * it if they match.  The operation of this method should be atomic,</span><br><span class="line"> * thus providing an uninterruptible way of updating an object field.</span><br><span class="line"> * 在obj的offset位置比较object field和期望的值，如果相同则更新。这个方法</span><br><span class="line"> * 的操作应该是原子的，因此提供了一种不可中断的方式更新object field。</span><br><span class="line"> * </span><br><span class="line"> * @param obj the object containing the field to modify.</span><br><span class="line"> *    包含要修改field的对象 </span><br><span class="line"> * @param offset the offset of the object field within &lt;code&gt;obj&lt;/code&gt;.</span><br><span class="line"> *         &lt;code&gt;obj&lt;/code&gt;中object型field的偏移量</span><br><span class="line"> * @param expect the expected value of the field.</span><br><span class="line"> *               希望field中存在的值</span><br><span class="line"> * @param update the new value of the field if it equals &lt;code&gt;expect&lt;/code&gt;.</span><br><span class="line"> *               如果期望值expect与field的当前值相同，设置filed的值为这个新值</span><br><span class="line"> * @return true if the field was changed.</span><br><span class="line"> *              如果field的值被更改</span><br><span class="line"> */</span><br><span class="line">public native boolean compareAndSwapObject(Object obj, long offset,</span><br><span class="line">                                           Object expect, Object update);</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;所以，加锁流程就变成了如下图的样子，</p>
<p><img src="/uploads/article/2021-07-19/加锁流程图.png" alt="加锁流程图.png"></p>
<p>&emsp;&emsp;根据这个流程，代码就变成了如下的样子：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">package com.example.myapplication;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * @ClassName: MyOwnLock</span><br><span class="line"> * @Description:</span><br><span class="line"> * @Author: 海晨忆</span><br><span class="line"> * @Date: 2021/7/15 11:07</span><br><span class="line"> */</span><br><span class="line">public class MyOwnLock &#123;</span><br><span class="line">    private volatile int state;</span><br><span class="line">    private Thread currentHolder;</span><br><span class="line"></span><br><span class="line">    public void lock() &#123;</span><br><span class="line">        if (acquire())&#123;</span><br><span class="line">            return;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public boolean acquire()&#123;</span><br><span class="line">        Thread currentThread = Thread.currentThread();</span><br><span class="line">        int state = getState();</span><br><span class="line">        if (state == 0) &#123;</span><br><span class="line">            if (compareAndSwapState(0,1)) &#123;</span><br><span class="line">                setCurrentHolder(currentThread);</span><br><span class="line">            &#125;</span><br><span class="line">            return true;</span><br><span class="line">        &#125;</span><br><span class="line">        return false;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void unLock() &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public int getState() &#123;</span><br><span class="line">        return state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setState(int state) &#123;</span><br><span class="line">        this.state = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Thread getCurrentHolder() &#123;</span><br><span class="line">        return currentHolder;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public void setCurrentHolder(Thread currentHolder) &#123;</span><br><span class="line">        this.currentHolder = currentHolder;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;加锁流程就完了吗？这才哪到哪，我们现在加锁就第一个拿到锁的线程开始用了，那后面没有拿到锁的线程怎么办呢？难道全部丢掉不管吗？</p>
<p>&emsp;&emsp;当然不行。那怎么办呢？我们先用一个队列，把没有拿到锁的线程存起来，排好队，等第一个线程执行完了之后，释放锁的时候，再直接唤醒等待的线程即可；</p>
<p>&emsp;&emsp;把阻塞的线程全放进队列（并且要线程安全，高并发的情况下迅速 入队，出队）中，当T1释放锁是，直接唤醒T2。队列选择用ConcurrentLinkedQueue<thread>（基于CAS算法，保证入队，出队安全）</thread></p>
<p>&emsp;&emsp;那么，要怎么做呢？要怎么等待呢？最简单的就是写一个死循环，一直循环去判断锁有没有用完。但是，这样会一直占用CPU，消耗性能。</p>
<ol>
<li>用sleep？Thread.sleep可能造成等待时间过长，你没法知道休眠的时间，如果，线程1执行完只需要200毫秒，你这里休眠了1000毫秒，那就浪费了800毫秒了。</li>
<li>那用Thread.yield()可以了吧？答案是不行，这个线程让步，虽然把cpu的时间片让出去给其他线程用了，但是，最后，我们需要唤醒的时候，怎么办呢？</li>
</ol>
<p>&emsp;&emsp;我们最后唤醒，是要指定唤醒哪一个线程，这里我们还是用到刚才Unsafe里面的两个方法，park和unPark。park阻塞线程，让出cpu的使用权，unPark解除阻塞，唤醒某一个线程。</p>
<p>&emsp;&emsp;这里我们用它的包装类LockSupport。所以，加锁代码就变成这样子了：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">private ConcurrentLinkedQueue&lt;Thread&gt; waiterQueue = new ConcurrentLinkedQueue&lt;&gt;();</span><br><span class="line"></span><br><span class="line">public void lock() &#123;</span><br><span class="line">    if (acquire()) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    Thread currentThread = Thread.currentThread();</span><br><span class="line">    waiterQueue.add(currentThread);</span><br><span class="line">    for (; ; ) &#123;</span><br><span class="line">        if (currentThread != waiterQueue.peek() &amp;&amp; acquire()) &#123;</span><br><span class="line">            waiterQueue.poll();</span><br><span class="line">            return;</span><br><span class="line">        &#125;</span><br><span class="line">        LockSupport.park(currentThread);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;获取锁的方法也要做相应的调整，如下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public boolean acquire() &#123;</span><br><span class="line">    Thread currentThread = Thread.currentThread();</span><br><span class="line">    int state = getState();</span><br><span class="line">    if (state == 0) &#123;</span><br><span class="line">        boolean temp = waiterQueue.size() == 0 || currentThread == waiterQueue.peek();</span><br><span class="line">        if (temp &amp;&amp; compareAndSwapState(0, 1)) &#123;</span><br><span class="line">            setCurrentHolder(currentThread);</span><br><span class="line">        &#125;</span><br><span class="line">        return true;</span><br><span class="line">    &#125;</span><br><span class="line">    return false;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;解锁就比较简单了</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public void unLock() &#123;</span><br><span class="line">    if (Thread.currentThread() != currentHolder) &#123;</span><br><span class="line">        throw new RuntimeException(&quot;出错啦&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">    int state = getState();</span><br><span class="line">    if (compareAndSwapState(state, 0)) &#123;</span><br><span class="line">        setCurrentHolder(null);</span><br><span class="line">        Thread firstThread = waiterQueue.peek();</span><br><span class="line">        if (firstThread != null) &#123;</span><br><span class="line">            LockSupport.unpark(firstThread);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;判断当前线程是不是持有锁的线程，如果不是，那就抛异常<br>如果是，那就获取状态state，通过cas算法，置换为0的状态。</p>
<p>&emsp;&emsp;最后运行完，结果如下：</p>
<p><img src="/uploads/article/2021-07-19/最终运行结果图.png" alt="最终运行结果图.png"></p>
<p>总结：上面我说的简单的线程同步器，只是AQS的一个主要的思想，CAS算法，计数器，线程队列。还有线程中断，独占模式，共享模式，公平锁，不公平锁等等。看完我这个，然后再去看源码应该会更容易懂。</p>

      
    </div>
    
      <footer class="article-footer">
        完
      </footer>
    
  </div>
  
    
<nav id="article-nav">
  <div class="article-nav-block">
    
      <a href="/2021/07/27/JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁/" id="article-nav-newer" class="article-nav-link-wrap">
        <strong class="article-nav-caption"></strong>
        <div class="article-nav-title">
          
            JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁
          
        </div>
      </a>
    
  </div>
  <div class="article-nav-block">
    
      <a href="/2021/07/07/Android模拟点击/" id="article-nav-older" class="article-nav-link-wrap">
        <div class="article-nav-title">Android模拟点击</div>
        <strong class="article-nav-caption"></strong>
      </a>
    
  </div>
</nav>

    
  
  
</article>
</section>
        <aside id="sidebar">
  
    <div class="widget-box">
  <div class="avatar-box avatar-item">
    <img class="avatar" src="/uploads/artistic_image/head.jpg" title="头像来自网络"></img>
    <h3 class="avatar-name">
      
        海晨忆
      
    </h3>
    <p class="avatar-slogan">
      先谋生，再谋爱。人间值得，未来可期。
    </p>
  </div>
</div>


  
    
  <div class="widget-box">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Android-Socket/">Android -Socket</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-加密算法/">Android -加密算法</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-常用功能/">Android -常用功能</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-框架/">Android -框架</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-源码解析/">Android -源码解析</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-自定义view/">Android -自定义view</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-设计模式/">Android -设计模式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA-并发/">JAVA -并发</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java-SpringBoot/">Java -SpringBoot</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Tomcat/">Tomcat</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/kotlin/">kotlin</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-JDBC/">数据库 -JDBC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-MySQL/">数据库 -MySQL</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-box">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/11/">November 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/10/">October 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/09/">September 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/08/">August 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/07/">July 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/05/">May 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/02/">February 2018</a></li></ul>
    </div>
  </div>

  
    
  <div class="widget-box">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2021/07/27/JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁/">JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁</a>
          </li>
        
          <li>
            <a href="/2021/07/19/JAVA—线程同步器AQS/">JAVA—线程同步器AQS</a>
          </li>
        
          <li>
            <a href="/2021/07/07/Android模拟点击/">Android模拟点击</a>
          </li>
        
          <li>
            <a href="/2019/11/27/SpringBoot系列-消息-RabbitMQ-（二十）/">SpringBoot系列-消息(RabbitMQ)（二十）</a>
          </li>
        
          <li>
            <a href="/2019/11/13/SpringBoot系列-Docker（十九）/">SpringBoot系列-Docker（十九）</a>
          </li>
        
      </ul>
    </div>
  </div>

  
      <div class="widget-box">
    <h3 class="widget-title">友情链接</h3>
    <div class="widget">
      
        <a class="hrf" style="display: block;" href="https://github.com/haichenyi" title target='_blank'
        >Github</a>
      
        <a class="hrf" style="display: block;" href="https://www.jianshu.com/u/6077ee440c37" title target='_blank'
        >简书</a>
      
        <a class="hrf" style="display: block;" href="https://blog.csdn.net/qq_27634797" title target='_blank'
        >CSDN</a>
      
    </div>
  </div>

  
</aside>
      </div>
      <footer id="footer">
  <div class="foot-box footers global-width">
    &copy;2017-2021 海晨忆 &nbsp;&nbsp;
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span id="busuanzi_container_site_pv">阁下是第<span id="busuanzi_value_site_pv"></span>个访客</span>
  </div>
</footer>
      <script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
if (!window.jQuery) {
var script = document.createElement('script');
script.src = "/js/jquery-2.0.3.min.js";
document.body.write(script);
}
</script>

  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



    </div>
    <nav id="mobile-nav" class="mobile-nav-box">
  <div class="mobile-nav-img mobile-nav-top"></div>
  
    <a href="/" class="mobile-nav-link">首页</a>
  
    <a href="/archives" class="mobile-nav-link">归档</a>
  
    <a href="/about" class="mobile-nav-link">简历</a>
  
  <div class="mobile-nav-img  mobile-nav-bottom"></div>
</nav>    
  </div>
</body>
</html>